package com.ksyun.campus.client;

import com.ksyun.campus.client.domain.AllocateBlocksResponse;
import com.ksyun.campus.client.domain.BlockInfo;
import com.ksyun.campus.client.domain.ReplicaInfo;
import com.ksyun.campus.client.util.HttpClientUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 文件系统输出流 - 分块写入实现
 * 
 * 职责分离设计：
 * - FSOutputStream：负责数据写入流程控制，向metaServer请求存储空间，向dataServer写入数据
 * - metaServer：负责块分配策略、块大小决策、副本选择等元数据管理
 * - dataServer：负责实际的数据存储
 */
public class FSOutputStream extends OutputStream {

    private static final Logger logger = LoggerFactory.getLogger(FSOutputStream.class);
    
    // JSON解析器
    private static final ObjectMapper objectMapper = new ObjectMapper();
    
    // 文件路径
    private final String path;
    
    // metaServer连接信息
    private final String metaServerHost;
    private final int metaServerPort;
    
    // 文件系统名称
    private final String fileSystemName;
    
    // 当前写入位置
    private long currentOffset;
    
    // 从metaServer获取的块信息（metaServer决定块的数量、大小和分布）
    private final List<BlockInfo> allocatedBlocks;
    
    // 当前块索引
    private int currentBlockIndex;
    

    
    // 已提交的块信息
    private final List<BlockCommitInfo> committedBlocks;
    
    // 流是否已关闭
    private boolean closed;

    /**
     * 构造函数
     */
    public FSOutputStream(String path, String metaServerHost, int metaServerPort, String fileSystemName) {
        this.path = path;
        this.metaServerHost = metaServerHost;
        this.metaServerPort = metaServerPort;
        this.fileSystemName = fileSystemName;
        this.currentOffset = 0;
        this.currentBlockIndex = 0;

        this.allocatedBlocks = new ArrayList<>();
        this.committedBlocks = new ArrayList<>();
        this.closed = false;
    }

    @Override
    public void write(int b) throws IOException {
        if (closed) {
            throw new IOException("Stream is closed");
        }
        
        // 将单个字节转换为字节数组
        byte[] data = new byte[]{(byte) b};
        write(data, 0, 1);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException("Stream is closed");
        }
        
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || off > b.length - len) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        
        // 一次性写入：向metaServer请求存储空间，然后将所有数据写入分配的块
        requestStorageFromMetaServer(len);
        
        // 将数据写入分配的块（currentOffset在writeBlockData中更新）
        writeAllDataToBlocks(b, off, len);
    }

    /**
     * 从metaServer请求分配块存储空间
     * 传递需要的字节数，让metaServer决定分配策略
     */
    private void requestStorageFromMetaServer(long neededBytes) throws IOException {
        String url = String.format("http://%s:%d/api/v1/meta/allocateBlocks", metaServerHost, metaServerPort);
        
        Map<String, String> headers = new HashMap<>();
        headers.put("fileSystemName", fileSystemName);
        headers.put("Content-Type", "application/json");
        
        // 告诉metaServer需要多少字节的存储空间，让它决定如何分配块
        String requestBody = String.format("{\"fileId\":\"%s\",\"bytes\":%d}", path, neededBytes);
        
        try {
            String response = HttpClientUtil.post(url, headers, requestBody);
            logger.debug("Request storage response: {}", response);
            
            // 解析metaServer响应中的块信息
            AllocateBlocksResponse allocateResponse = objectMapper.readValue(response, AllocateBlocksResponse.class);
            
            if (!allocateResponse.isSuccess()) {
                throw new IOException("Failed to request storage from metaServer: " + allocateResponse.getMessage());
            }
            
            if (allocateResponse.getData() != null && allocateResponse.getData().getBlocks() != null 
                && !allocateResponse.getData().getBlocks().isEmpty()) {
                // 将metaServer分配的块添加到列表中
                allocatedBlocks.addAll(allocateResponse.getData().getBlocks());
                logger.debug("MetaServer为文件{}分配了{}个块（{}字节）", 
                    path, allocateResponse.getData().getBlocks().size(), neededBytes);
            } else {
                throw new IOException("No blocks returned from metaServer");
            }
            
        } catch (Exception e) {
            logger.error("Failed to request storage from metaServer", e);
            throw new IOException("Failed to request storage from metaServer", e);
        }
    }

    /**
     * 将所有数据一次性写入分配的块
     * metaServer已经精确计算好了每个块的大小和数据分布，客户端只需按顺序写入
     */
    private void writeAllDataToBlocks(byte[] data, int offset, int length) throws IOException {
        int dataOffset = 0;
        
        // 按顺序写入每个分配的块，metaServer已经计算好了精确的块大小
        for (BlockInfo block : allocatedBlocks) {
            int blockDataSize = (int) block.getSize();
            
            // 写入当前块的全部数据
            writeBlockData(data, offset + dataOffset, blockDataSize);
            
            // 移动到下一块的数据
            dataOffset += blockDataSize;
            currentBlockIndex++;
        }
        
        // 验证所有数据都已写入
        if (dataOffset != length) {
            throw new IOException(String.format("Data size mismatch: expected %d, written %d", length, dataOffset));
        }
    }

    /**
     * 将块数据写入dataServer - 使用块级存储API
     * 直接调用DataServer的块级写入接口，传递fileId和blkIdx
     */
    private void writeBlockData(byte[] data, int offset, int length) throws IOException {
        BlockInfo blockInfo = allocatedBlocks.get(currentBlockIndex);
        if (blockInfo.getReplicas() == null || blockInfo.getReplicas().isEmpty()) {
            throw new IOException("No replicas available for block " + blockInfo.getBlkIdx());
        }
        
        // 选择主副本
        ReplicaInfo targetReplica = null;
        for (ReplicaInfo replica : blockInfo.getReplicas()) {
            if ("PRIMARY".equals(replica.getRole())) {
                targetReplica = replica;
                break;
            }
        }
        
        // 如果没有找到主副本，选择第一个可用副本
        if (targetReplica == null) {
            targetReplica = blockInfo.getReplicas().get(0);
        }
        
        // 使用块级写入API
        String writeUrl = String.format("http://%s:%d/api/v1/data/writeBlockWithData?fileId=%s&blkIdx=%d&isPrimary=true",
            targetReplica.getHost(), targetReplica.getPort(), 
            blockInfo.getFileId(), blockInfo.getBlkIdx());
        
        Map<String, String> writeHeaders = new HashMap<>();
        writeHeaders.put("Content-Type", "application/octet-stream");
        writeHeaders.put("X-Client-Type", "minfs-client");
        
        // 提取要写入的数据段
        byte[] writeData = new byte[length];
        System.arraycopy(data, offset, writeData, 0, length);
        
        try {
            String writeResponse = HttpClientUtil.post(writeUrl, writeHeaders, writeData);
            logger.debug("Write block using block API - fileId: {}, blkIdx: {}, size: {}, response: {}", 
                blockInfo.getFileId(), blockInfo.getBlkIdx(), length, writeResponse);
            
            // 验证响应
            if (writeResponse == null || writeResponse.trim().isEmpty()) {
                throw new IOException("Empty response from dataServer");
            }
            
            // 检查响应是否成功
            if (writeResponse.contains("\"success\":false") || writeResponse.contains("error")) {
                throw new IOException("Block write failed: " + writeResponse);
            }
            
            // 记录块信息用于提交
            committedBlocks.add(new BlockCommitInfo(blockInfo.getBlkIdx(), (long) length));
            
        } catch (Exception e) {
            logger.error("Failed to write block data to {} using block API", targetReplica.getHost(), e);
            throw new IOException("Failed to write block data using block API", e);
        }
        
        // 更新全局偏移量
        currentOffset += length;
    }
    




    @Override
    public void close() throws IOException {
        if (closed) {
            return;
        }
        
        try {
            // 调用metaServer写入API来提交文件信息
            commitFileInfo();
            
        } finally {
            closed = true;
            // 清理资源
            allocatedBlocks.clear();
        }
    }

    /**
     * 调用metaServer提交API来提交文件信息
     */
    private void commitFileInfo() throws IOException {
        String url = String.format("http://%s:%d/api/v1/meta/commit", metaServerHost, metaServerPort);
        
        Map<String, String> headers = new HashMap<>();
        headers.put("fileSystemName", fileSystemName);
        headers.put("Content-Type", "application/json");
        
        // 构建提交请求体
        StringBuilder blocksJson = new StringBuilder("[");
        for (int i = 0; i < committedBlocks.size(); i++) {
            BlockCommitInfo block = committedBlocks.get(i);
            if (i > 0) blocksJson.append(",");
            blocksJson.append(String.format("{\"blkIdx\":%d,\"size\":%d}", block.getBlkIdx(), block.getSize()));
        }
        blocksJson.append("]");
        
        String requestBody = String.format(
            "{\"fileId\":\"%s\",\"size\":%d,\"blocks\":%s,\"checksum\":\"\"}", 
            path, currentOffset, blocksJson.toString());
        
        try {
            String response = HttpClientUtil.post(url, headers, requestBody);
            logger.info("Commit file info response: {}", response);
        } catch (Exception e) {
            logger.error("Failed to commit file info", e);
            throw new IOException("Failed to commit file info", e);
        }
    }

    /**
     * 块提交信息
     */
    private static class BlockCommitInfo {
        private final int blkIdx;
        private final long size;  // 与服务端CommitRequest.BlockCommitInfo.size类型保持一致
        
        public BlockCommitInfo(int blkIdx, long size) {
            this.blkIdx = blkIdx;
            this.size = size;
        }
        
        public int getBlkIdx() {
            return blkIdx;
        }
        
        public long getSize() {
            return size;
        }
    }
}
